//========================================================================
//$Id: IJettyService.java 387 2011-02-07 05:19:35Z janb.webtide $
//Copyright 2008 Mort Bay Consulting Pty. Ltd.
//------------------------------------------------------------------------
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at 
//http://www.apache.org/licenses/LICENSE-2.0
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
//========================================================================

package org.mortbay.ijetty;

import java.io.File;
import java.io.InputStream;

import org.mortbay.ijetty.util.IJettyToast;
import org.mortbay.jetty.Handler;
import org.mortbay.jetty.HttpGenerator;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.bio.SocketConnector;
import org.mortbay.jetty.handler.ContextHandlerCollection;
import org.mortbay.jetty.handler.DefaultHandler;
import org.mortbay.jetty.handler.HandlerCollection;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.res.Resources;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.util.Log;

/**
 * IJettyService
 *
 * Android Service which runs the Jetty server, maintaining it in the active Notifications so that
 * the user can return to the IJetty Activity to control it at any time.
 */
public class IJettyService extends Service implements Task
{
	public boolean started = false;
    private static Resources __resources;
    private static final String CONTENT_RESOLVER_ATTRIBUTE = "org.mortbay.ijetty.contentResolver";
    private static final String ANDROID_CONTEXT_ATTRIBUTE = "org.mortbay.ijetty.context"; 
    
    public static final int __START_PROGRESS_DIALOG = 0;
    public static final int __STARTED = 0;
    public static final int __NOT_STARTED = 1;
    public static final int __STOPPED = 2;
    public static final int __NOT_STOPPED = 3;
    public static final int __STARTING = 4;
    public static final int __STOPPING = 5;
    
    public static final String[] __configurationClasses = 
        new String[]
        {
            "org.mortbay.ijetty.AndroidWebInfConfiguration",
            "org.mortbay.jetty.webapp.WebXmlConfiguration",
            "org.mortbay.jetty.webapp.JettyWebXmlConfiguration",
            "org.mortbay.jetty.webapp.TagLibConfiguration" 
        };
 
    private NotificationManager mNM;
    private Server server;
    private ContextHandlerCollection contexts;
    private int _port;
    private SharedPreferences preferences;
    private PackageInfo pi;
    private android.os.Handler _handler;

    PowerManager.WakeLock wakeLock;
    
    private ServiceBinder binder = new ServiceBinder();
    private BroadcastReceiver receiver = new BroadcastReceiver() {
		
		@Override
		public void onReceive(Context context, Intent intent) {			
			IJettyService.this.sendBoardCast();
		}
	};
    
	private void sendBoardCast() {
		Intent it = new Intent("com.ijetty.service.start");
		if(started) {
			it.putExtra("started", true);
		} else {
			it.putExtra("started", false);
		}
		this.sendBroadcast(it);
    }
	
    public class JettyStarterThread extends Thread
    {
        android.os.Handler _handler;
        
        public JettyStarterThread(android.os.Handler handler)
        {
            _handler = handler;
        }
        public void run ()
        {
            try
            {
                sendMessage(__STARTING);
                startJetty();
                sendMessage(__STARTED);
              
                Log.i("Jetty", "Jetty started");
            }
            catch (Exception e)
            {
                sendMessage(__NOT_STARTED);
                Log.e("Jetty", "Error starting jetty", e);
                
            }
        }
        
        public void sendMessage(int state)
        {
            Message msg = _handler.obtainMessage();
            Bundle b = new Bundle();
            b.putInt("state", state);
            msg.setData(b);
            _handler.sendMessage(msg);
        }
    }
    
    
    public class JettyStopperThread extends Thread
    { 
        android.os.Handler _handler;
        
        public JettyStopperThread(android.os.Handler handler)
        {
            _handler = handler;
        }
        
        public void run ()
        {
            try
            {
                sendMessage(__STOPPING);
                stopJetty();
                Log.i("Jetty", "Jetty stopped");
                sendMessage(__STOPPED);
               
            }
            catch (Exception e)
            {
                
                sendMessage(__NOT_STOPPED);
                Log.e("Jetty", "Error stopping jetty", e);
            }
        }
        
        public void sendMessage(int state)
        {
            Message msg = _handler.obtainMessage();
            Bundle b = new Bundle();
            b.putInt("state", state);
            msg.setData(b);
            _handler.sendMessage(msg);
        }
    }
    
    
    public IJettyService()
    {
        super();
        _handler = new android.os.Handler ()
        {
            public void handleMessage(Message msg) {
            	Log.i("service", "handler");
                switch (msg.getData().getInt("state"))
                {
                    case __STARTED:
                    {
                        IJettyToast.showServiceToast(IJettyService.this,R.string.jetty_started);
                        mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                        // The PendingIntent to launch IJetty activity if the user selects this notification
                        PendingIntent contentIntent = PendingIntent.getActivity(IJettyService.this, 0,
                                new Intent(IJettyService.this, IJetty.class), 0);

                        CharSequence text = getText(R.string.manage_jetty);

                        Notification notification = new Notification(R.drawable.state, 
                                text, 
                                System.currentTimeMillis());

                        notification.setLatestEventInfo(IJettyService.this, getText(R.string.app_name),
                                text, contentIntent);

                        mNM.notify(R.string.jetty_started, notification);
                        break;
                    }
                    case __NOT_STARTED:
                    {
                        IJettyToast.showServiceToast(IJettyService.this,R.string.jetty_not_started);
                        break;
                    }
                    case __STOPPED:
                    {
                        // Cancel the persistent notification.
                        mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                        mNM.cancel(R.string.jetty_started);
                        // Tell the user we stopped.
                        IJettyToast.showServiceToast(IJettyService.this,R.string.jetty_stopped);
                       
                        break;
                    }
                    
                    case __NOT_STOPPED:
                    {
                        IJettyToast.showServiceToast(IJettyService.this,R.string.jetty_not_stopped);
                        break;
                    }
                    case __STARTING:
                    {
                        IJettyToast.showServiceToast(IJettyService.this,R.string.jetty_starting);
                        break;
                    }                    
                    case __STOPPING:
                    {
                        IJettyToast.showServiceToast(IJettyService.this,R.string.jetty_stopping);
                        break;
                    }
                }
               
            }
 
        };
    }
    /** 
     * Android Service create
     * @see android.app.Service#onCreate()
     */
    public void onCreate()
    {
    	this.registerReceiver(receiver, new IntentFilter("com.ijetty.activity.started"));
    	Log.i("service", "onCreate" + "@" + started);
        __resources = getResources();

        try
        {
            pi = getPackageManager().getPackageInfo(getPackageName(), 0); 
        }
        catch (Exception e)
        {
            Log.e("Jetty", "Unable to determine running jetty version");
        }
    }


    /** 
     * Android Service Start
     * @see android.app.Service#onStart(android.content.Intent, int)
     */
    public void onStart(Intent intent, int startId)
    {
    	started = true;
    	Log.i("service", "onStart" + "@" + started);
        if (server != null)
        {
            IJettyToast.showServiceToast(IJettyService.this,R.string.jetty_already_started);
            return;
        }

        try
        {
            preferences = PreferenceManager.getDefaultSharedPreferences(this);

            String portDefault = getText(R.string.pref_port_value).toString();
            String portKey = getText(R.string.pref_port_key).toString();
            _port = Integer.parseInt(preferences.getString(portKey, portDefault));

            Log.d("Jetty", "pref port = "+_port);
            //Get a wake lock to stop the cpu going to sleep
            PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
            wakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "IJetty");
            wakeLock.acquire();

            new JettyStarterThread(_handler).start();

            
            super.onStart(intent, startId);
        }
        catch (Exception e)
        {
            Log.e("Jetty", "Error starting jetty", e);
            IJettyToast.showServiceToast(IJettyService.this,R.string.jetty_not_started);
        }
    }


    /** 
     * Android Service destroy
     * @see android.app.Service#onDestroy()
     */
    public void onDestroy()
    {
    	this.unregisterReceiver(receiver);
    	Log.i("service", "onDestroy" + "@" + started);
        try
        {
            if (wakeLock != null)
            {
                wakeLock.release();
                wakeLock = null;
            }
            
            if (server != null)
            {
                new JettyStopperThread(_handler).start();
                
            }
            else
            {
                Log.i("Jetty", "Jetty not running");
                IJettyToast.showServiceToast(IJettyService.this,R.string.jetty_not_running);
            }
        }
        catch (Exception e)
        {
            Log.e("Jetty", "Error stopping jetty", e);
            IJettyToast.showServiceToast(IJettyService.this,R.string.jetty_not_stopped);
        }
    }
    
    
   
    

    public void onLowMemory()
    {
        Log.i("Jetty", "Low on memory");
        super.onLowMemory();
    }


    /**
     * Hack to get around bug in ResourceBundles
     * 
     * @param id
     * @return
     */
    public static InputStream getStreamToRawResource(int id)
    {
        if (__resources != null)
            return __resources.openRawResource(id);
        else
            return null;
    }

    public IBinder onBind(Intent intent)
    {
    	started = true;
    	Log.i("service", "onBind" + "@" + started);
        return binder;
    }

    
    /**
     * Get a reference to the Jetty Server instance
     * @return
     */
    public Server getServer()
    {
        return server;
    }
    
    
    protected Server newServer()
    {
        return new Server();
    }
    
    protected ContextHandlerCollection newContexts()
    {
        return new ContextHandlerCollection();
    }
  
    
    protected void configureConnectors()
    {
        if (server != null)
        {
             SocketConnector bioConnector = new SocketConnector();
             bioConnector.setPort(_port);
             bioConnector.setMaxIdleTime(3000);
             server.addConnector(bioConnector);
        }
    }
    
    protected void configureHandlers()
    {
        if (server != null)
        {
            HandlerCollection handlers = new HandlerCollection();
            contexts = new ContextHandlerCollection();
            handlers.setHandlers(new Handler[] {contexts, new DefaultHandler()});
            server.setHandler(handlers);
        }
    }
    
    protected void configureDeployers () throws Exception
    {
        AndroidWebAppDeployer staticDeployer =  new AndroidWebAppDeployer();
        //AndroidContextDeployer contextDeployer = new AndroidContextDeployer();
     
        File jettyDir = IJetty.__JETTY_DIR;
        
        // Load any webapps we find on the card.
        if (jettyDir.exists())
        {
            // Deploy any static webapps we have.
            if (new File(jettyDir, IJetty.__WEBAPP_DIR).exists())
            {
                staticDeployer.setWebAppDir(IJetty.__JETTY_DIR+"/"+IJetty.__WEBAPP_DIR);
                //staticDeployer.setDefaultsDescriptor(IJetty.__JETTY_DIR+"/"+IJetty.__ETC_DIR+"/webdefault.xml");
                staticDeployer.setContexts(contexts);
                staticDeployer.setAttribute(CONTENT_RESOLVER_ATTRIBUTE, getContentResolver());
                staticDeployer.setAttribute(ANDROID_CONTEXT_ATTRIBUTE, (Context) IJettyService.this);
                staticDeployer.setConfigurationClasses(__configurationClasses);
                staticDeployer.setAllowDuplicates(false);
            }          
        }
        else
        {
            Log.w("Jetty", "Not loading any webapps - none on SD card.");
        }

        if (server != null)
        {
            //server.addLifeCycle(contextDeployer);
            server.addLifeCycle(staticDeployer); 
        }
    }
    
    protected void startJetty() throws Exception
    {
        // Bridge Jetty logging to Android logging
        AndroidLog.__isDebugEnabled = false;
        System.setProperty("org.mortbay.log.class","org.mortbay.log.AndroidLog");
        org.mortbay.log.Log.setLog(new AndroidLog());

        //Set jetty.home
        System.setProperty ("jetty.home", IJetty.__JETTY_DIR.getAbsolutePath());
        
        server = newServer();
        
        configureConnectors();
        configureHandlers();
        configureDeployers();
        //configureRealm ();

        server.start();
        
        // Less than ideal solution to the problem that dalvik doesn't know about manifests of jars.
        // A as the version field is private to Server, its difficult
        //if not impossible to set it any other way. Note this means that ContextHandler.SContext.getServerInfo()
        //will still return 0.0.
        HttpGenerator.setServerVersion("i-jetty "+pi.versionName);
    }

    protected void stopJetty() throws Exception
    {
        try
        {
            Log.i("Jetty", "Jetty stopping");
            server.stop();
            Log.i("Jetty", "Jetty server stopped");
            server = null;
            __resources = null;
        }
        finally
        {
            Log.i("Jetty","Finally stopped");
        }
    }
    
    public class ServiceBinder extends Binder implements Task {

		@Override
		public boolean isStarted() {
			Log.i("service", String.valueOf(started));
			return started;
		}
    	
    }


	@Override
	public boolean isStarted() {
		return this.started;
	}
   
}
